package com.doupi.partner.manager.gateway.wx;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.doupi.partner.util.HttpUtils;
import com.doupi.partner.util.RedisUtils;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 微信工具类
 * 
 * @author Super Jens Wang
 * @version 1.0 on 16/9/16 下午6:13.
 */
@Component
public class WxUtils {

	private final static Logger LOGGER = LoggerFactory.getLogger(WxUtils.class);

	private final static String ACCESS_TOKEN = "WX_ACCESS_TOKEN";

	private final static String TICKET_TOKEN = "WX_TICKET_TOKEN";

	private final static String AUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirect_uri={1}&response_type={2}&scope={3}&state={4}{5}";

	private final static String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type={0}&appid={1}&secret={2}";

	private final static String TIKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type={1}";

	private final static String USER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token={0}&openid={1}&lang={2}";

	private final static String DOWNLOAD_MEDIA_URL = "https://api.weixin.qq.com/cgi-bin/media/get?access_token={0}&media_id={1}";

	private final static String WEB_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={0}&secret={1}&code={2}&grant_type={3}";

	private final static String WEB_ACCESS_TOKEN_REFRESH_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid={0}&grant_type={1}&refresh_token={2}";

	private final static String WEB_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token={0}&openid={1}&lang={2}";

	private final static String WEB_ACCESS_TOKEN_CHECK_URL = "https://api.weixin.qq.com/sns/auth?access_token={0}&openid={1}";

	private final static String TEMPLATE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={0}";

	@Value("#{doupi_partner_util_config.wx_app_id}")
	private String appId;

	@Value("#{doupi_partner_util_config.wx_redirect_uri}")
	private String redirectUri;

	@Value("#{doupi_partner_util_config.wx_scope}")
	private String scope;

	@Value("#{doupi_partner_util_config.wx_secret}")
	private String secret;

	@Value("#{doupi_partner_util_config.wx_sign_token}")
	private String signToken;

	/**
	 * 支付秘钥
	 */
	@Value("#{doupi_partner_util_config.wx_pay_key}")
	private String payKey;

	/**
	 * 商户
	 */
	@Value("#{doupi_partner_util_config.wx_mch_id}")
	private String mchId;

	/**
	 * 证书位置
	 */
	@Value("#{doupi_partner_util_config.wx_cert_local_path}")
	private String certLocalPath;

	/**
	 * 证书密码
	 */
	@Value("#{doupi_partner_util_config.wx_cert_password}")
	private String certPassword;

	@Value("#{doupi_partner_util_config.wx_pay_debug}")
	private Boolean payDebug;

	@Value("#{doupi_partner_util_config.wx_pay_notify}")
	private String payNotify;

	@Value("#{doupi_partner_util_config.wx_pay_body}")
	private String payBody;

	@Value("#{doupi_partner_util_config.ftp_path}")
	private String uploadPath;

	private final RedisUtils redisUtils;

	private final HttpUtils httpUtils;

	@Autowired
	public WxUtils(RedisUtils redisUtils, HttpUtils httpUtils) {
		this.redisUtils = redisUtils;
		this.httpUtils = httpUtils;
	}

	/**
	 * 获取认证地址
	 */
	public String getAuthUrl(String state) {
		try {
			return MessageFormat.format(AUTH_URL, appId, URLEncoder.encode(redirectUri, CharEncoding.UTF_8), "code", scope, state, "#wechat_redirect");
		} catch (Exception e) {
			LOGGER.error("获取跳转认证地址失败", e);
		}
		return null;
	}

	/**
	 * 生成JSSDK配置
	 */
	public Map<String, String> genenralTicketConfig(String url) {
		try {
			return getConfig(getTicket(getAccesstoken(), "jsapi"), url);
		} catch (Exception e) {
			LOGGER.error("生成JSSDK的ticket失败");
		}
		return null;
	}

	/**
	 * 通过用户基本信息获取用户信息，没有关注获取不到其他信息
	 */
	public Map<String, Object> getUserInfo(String openId, String lang) {
		try {
			String urlString = MessageFormat.format(USER_INFO_URL, getAccesstoken(), openId, lang);
			LOGGER.info("[WxUtil] - 获取用户信息数据URL:{}", urlString);
			URL url = new URL(urlString);
			String response = IOUtils.toString(url);
			LOGGER.info("[WxUtil] - 获取用户信息数据:{}", response);
			JSONObject jsonObject = JSON.parseObject(response);
			Map<String, Object> userInfos = new HashMap<String, Object>();
			userInfos.putAll(jsonObject);
			return userInfos;
		} catch (Exception e) {
			LOGGER.error("[WxUtils] -- 获取用户信息错误", e);
		}
		return java.util.Collections.emptyMap();
	}

	/**
	 * 通过web授权获取用户信息，不需要用户关注
	 */
	public Map<String, Object> getWebUserInfo(String code, String openId, String lang) {
		try {
			JSONObject userInfoToken = getWebUserInfoAccessToken(openId, code);
			String urlString = MessageFormat.format(WEB_USER_INFO_URL, userInfoToken.getString("access_token"), openId, lang);
			LOGGER.info("[WxUtil] - 获取用户信息数据URL:{}", urlString);
			URL url = new URL(urlString);
			String response = IOUtils.toString(url);
			LOGGER.info("[WxUtil] - 获取用户信息数据:{}", response);
			JSONObject jsonObject = JSON.parseObject(response);
			Map<String, Object> userInfos = new HashMap<String, Object>();
			userInfos.putAll(jsonObject);
			return userInfos;
		} catch (Exception e) {
			LOGGER.error("[WxUtils] -- 获取用户信息错误", e);
		}
		return java.util.Collections.emptyMap();
	}

	/**
	 * 通过web授权获取OPEN_ID
	 */
	public Map<String, Object> getOpenId(String code) {
		try {
			Map<String, Object> maps = new HashMap<String, Object>();
			String urlString = MessageFormat.format(WEB_ACCESS_TOKEN_URL, appId, secret, code, "authorization_code");
			LOGGER.info("[WxUtil] -- 获取OPEN_ID，授权URL:{}", urlString);
			URL url = new URL(urlString);
			String response = IOUtils.toString(url);
			LOGGER.info("[WxUtil] -- 获取OPEN_ID，授权响应内容:{}", response);
			JSONObject jsonObject = JSON.parseObject(response);
			maps.putAll(jsonObject);

			String userInfoTokenKey = jsonObject.getString("openid") + "_USER_INFO_TOKEN";
			int expires_in = jsonObject.getInteger("expires_in");
			redisUtils.setex(userInfoTokenKey, JSON.toJSONString(jsonObject), expires_in);
			return maps;
		} catch (Exception e) {
			LOGGER.error("[WxUtil] -- 获取微信OPENID失败", e);
		}
		return null;
	}

	/**
	 * 下载文件
	 */
	public String mediaDownload(String mediaId) {
		try {
			String urlString = MessageFormat.format(DOWNLOAD_MEDIA_URL, getAccesstoken(), mediaId);
			LOGGER.info("下载文件地址:{}", urlString);
			URL url = new URL(urlString);
			URLConnection conn = url.openConnection();
			String contentDisposition = conn.getHeaderField("Content-disposition");
			LOGGER.info("下载的文件是：{}", contentDisposition);
			String fileName = StringUtils.substringBetween(contentDisposition, "\"", "\"");
			byte[] data = IOUtils.toByteArray(url);
			File file = new File(uploadPath + "wxmedia" + File.separator + fileName);
			FileUtils.writeByteArrayToFile(file, data);
			return file.getAbsolutePath();
		} catch (Throwable e) {
			LOGGER.error("下载资源失败", e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * 发送模板消息
	 * 
	 * touser 是 接收者openid
	 * 
	 * template_id 是 模板ID
	 * 
	 * url 否 模板跳转链接
	 * 
	 * data 是 模板数据 {"key":{value:"", color:"#173177"}}
	 */
	public void pushMessageToUserByTemplateId(String touser, String templateId, String url, Map<String, MsgData> data) {
		try {
			String postUrl = MessageFormat.format(TEMPLATE_MSG_SEND_URL, getAccesstoken());
			LOGGER.info("发送消息地址：{}", postUrl);
			Map<String, Object> params = new HashMap<String, Object>();
			params.put("touser", touser);
			params.put("template_id", templateId);
			params.put("url", url);
			params.put("data", data);
			String repsonse = httpUtils.sendPost(postUrl, JSON.toJSONString(params));
			LOGGER.info("消息发送响应：{}", repsonse);
		} catch (Exception e) {
			LOGGER.error("发送消息失败", e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * 消息发送实体
	 * 
	 * @author Super Jens Wang
	 *
	 * @version 1.0 @ 2016年11月20日 下午10:10:09
	 */
	public static class MsgData {
		public MsgData() {
		}

		public String color;
		public String value;
	}

	/**
	 * 获取access_token
	 */
	private String getAccesstoken() throws Exception {
		String accessToken = redisUtils.get(ACCESS_TOKEN);
		if (StringUtils.isBlank(accessToken)) {
			URL url = new URL(MessageFormat.format(TOKEN_URL, "client_credential", appId, secret));
			String response = IOUtils.toString(url);
			LOGGER.info("[WxUtils] - 获取access token数据:{}", response);
			JSONObject jsonObject = JSON.parseObject(response);
			accessToken = jsonObject.getString("access_token");
			int expiresIn = Integer.parseInt(jsonObject.getString("expires_in"));
			redisUtils.setex(ACCESS_TOKEN, accessToken, expiresIn / 2);
		}
		return accessToken;
	}

	/**
	 * 获取ticket
	 */
	private String getTicket(String access_token, String type) throws Exception {
		String ticket = redisUtils.get(TICKET_TOKEN);
		if (StringUtils.isBlank(ticket)) {
			URL url = new URL(MessageFormat.format(TIKET_URL, access_token, type));
			String response = IOUtils.toString(url);
			LOGGER.info("[WxUtil] - 获取ticket数据:{}", response);
			JSONObject jsonObject = JSON.parseObject(response);
			ticket = jsonObject.getString("ticket");
			int expiresIn = Integer.parseInt(jsonObject.getString("expires_in"));
			redisUtils.setex(TICKET_TOKEN, ticket, expiresIn);
		}
		return ticket;
	}

	/**
	 * 获取JSSDK的配置
	 */
	private Map<String, String> getConfig(String jsapi_ticket, String url) {
		Map<String, String> ret = new HashMap<String, String>();
		String nonce_str = create_nonce_str();
		String timestamp = create_timestamp();
		String signature = "";
		String parmas = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "&timestamp=" + timestamp + "&url=" + url;
		LOGGER.info("titket原始参数：{}", parmas);
		byte[] data = DigestUtils.getSha1Digest().digest(org.apache.commons.codec.binary.StringUtils.getBytesUtf8(parmas));
		signature = Hex.encodeHexString(data);
		ret.put("url", url);
		ret.put("jsapi_ticket", jsapi_ticket);
		ret.put("nonceStr", nonce_str);
		ret.put("timestamp", timestamp);
		ret.put("signature", signature);
		return ret;
	}

	/**
	 * 获取web授权用户信息token
	 */
	private JSONObject getWebUserInfoAccessToken(String openId, String code) {
		try {
			String userInfoTokenKey = openId + "_USER_INFO_TOKEN";
			String userInfoTokenCache = redisUtils.get(userInfoTokenKey);
			JSONObject userInfoToken;
			if (StringUtils.isNotBlank(userInfoTokenCache)) {
				userInfoToken = JSON.parseObject(userInfoTokenCache);
				if (!checkUserInfoToken(userInfoToken.getString("access_token"), openId)) { // 刷新token
					String urlString = MessageFormat.format(WEB_ACCESS_TOKEN_REFRESH_URL, appId, "refresh_token", userInfoToken.getString("refresh_token"));
					LOGGER.info("[WxUtil] - 刷新用户信息ACCESS_TOKEN地址", urlString);
					URL url = new URL(urlString);
					String response = IOUtils.toString(url);
					LOGGER.info("[WxUtil] -[WxUtil] - 刷新用户信息ACCESS_TOKEN返回信息{}", response);
					userInfoToken = JSON.parseObject(response);
					if (StringUtils.isBlank(userInfoToken.getString("errcode"))) {
						int expiresIn = userInfoToken.getInteger("expires_in");
						redisUtils.setex(userInfoTokenKey, JSON.toJSONString(userInfoToken), expiresIn);
						return userInfoToken;
					}
				}
				return userInfoToken;
			}

			// 重新获取
			String urlString = MessageFormat.format(WEB_ACCESS_TOKEN_URL, appId, secret, code, "authorization_code");
			LOGGER.info("[WxUtil] - 获取用户信息ACCESS_TOKEN地址", urlString);
			URL url = new URL(urlString);
			String response = IOUtils.toString(url);
			LOGGER.info("[WxUtil] -[WxUtil] - 获取用户信息ACCESS_TOKEN返回信息{}", response);
			userInfoToken = JSON.parseObject(response);
			int expires_in = userInfoToken.getInteger("expires_in");
			redisUtils.setex(userInfoTokenCache, JSON.toJSONString(userInfoToken), expires_in);
			return userInfoToken;
		} catch (Exception e) {
			LOGGER.error("刷新用户信息失败", e);
		}
		return null;
	}

	/**
	 * 校验web授权用户信息的ACCESS_TOKEN是否有效
	 */
	private boolean checkUserInfoToken(String accessToken, String openId) {
		try {
			String urlString = MessageFormat.format(WEB_ACCESS_TOKEN_CHECK_URL, accessToken, openId);
			URL url = new URL(urlString);
			String response = IOUtils.toString(url);
			LOGGER.info("[WxUtils] -- 校验用户TOKEN,URL{}, 返回信息{}", urlString, response);
			JSONObject jsonObject = JSON.parseObject(response);
			return jsonObject.getInteger("errcode") == 0;
		} catch (Exception e) {
			LOGGER.error("[WxUtils] -- 校验用户TOKEN失败", e);
		}
		return false;
	}

	private String create_nonce_str() {
		return UUID.randomUUID().toString();
	}

	private static String create_timestamp() {
		return Long.toString(System.currentTimeMillis() / 1000);
	}

	public String getAppId() {
		return appId;
	}

	public void setAppId(String appId) {
		this.appId = appId;
	}

	public String getRedirectUri() {
		return redirectUri;
	}

	public void setRedirectUri(String redirectUri) {
		this.redirectUri = redirectUri;
	}

	public String getScope() {
		return scope;
	}

	public void setScope(String scope) {
		this.scope = scope;
	}

	public String getSecret() {
		return secret;
	}

	public void setSecret(String secret) {
		this.secret = secret;
	}

	public String getSignToken() {
		return signToken;
	}

	public void setSignToken(String signToken) {
		this.signToken = signToken;
	}

	public String getPayKey() {
		return payKey;
	}

	public void setPayKey(String payKey) {
		this.payKey = payKey;
	}

	public String getMchId() {
		return mchId;
	}

	public void setMchId(String mchId) {
		this.mchId = mchId;
	}

	public String getCertLocalPath() {
		return certLocalPath;
	}

	public void setCertLocalPath(String certLocalPath) {
		this.certLocalPath = certLocalPath;
	}

	public String getCertPassword() {
		return certPassword;
	}

	public void setCertPassword(String certPassword) {
		this.certPassword = certPassword;
	}

	public Boolean getPayDebug() {
		return payDebug;
	}

	public void setPayDebug(Boolean payDebug) {
		this.payDebug = payDebug;
	}

	public String getPayNotify() {
		return payNotify;
	}

	public void setPayNotify(String payNotify) {
		this.payNotify = payNotify;
	}

	public String getPayBody() {
		return payBody;
	}

	public void setPayBody(String payBody) {
		this.payBody = payBody;
	}

	public String getUploadPath() {
		return uploadPath;
	}

	public void setUploadPath(String uploadPath) {
		this.uploadPath = uploadPath;
	}
}
